/*!
 * @file MouseActuator.cpp
 * @author Rocco Martino
 */
/***************************************************************************
 *   Copyright (C) 2014 by Rocco Martino                                   *
 *   martinorocco@gmail.com                                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2.1 of the  *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

/* ======================================================================= */
/* ....................................................................... */
#include <ooGame/MouseActuator>

#include <osgODE/Notify>
#include <osgODE/World>
#include <osgODE/RigidBody>

#include <osgGA/GUIActionAdapter>
/* ....................................................................... */
/* ======================================================================= */




using namespace ooGame ;




/* ======================================================================= */
/* ....................................................................... */
MouseActuator::MouseActuator(void):
    m_mode                      ( LOOK ),
    m_use_axis_x                ( true ),
    m_sensitivity_x             ( 1.0 ),
    m_threshold_x               ( 0.0 ),
    m_min_x                     ( -FLT_MAX ),
    m_max_x                     ( FLT_MAX ),
    m_object_axis_x             ( osg::Z_AXIS ),
    m_local_x                   ( false ),
    m_reset_x                   ( true ),
    m_use_axis_y                ( true ),
    m_sensitivity_y             ( 1.0 ),
    m_threshold_y               ( 0.0 ),
    m_min_y                     ( -FLT_MAX ),
    m_max_y                     ( FLT_MAX ),
    m_object_axis_y             ( osg::X_AXIS ),
    m_local_y                   ( true ),
    m_reset_y                   ( true ),
    m_accum_x_internal          ( 0.0 ),
    m_accum_y_internal          ( 0.0 ),
    m_accum_x_motion_internal   ( 0.0 ),
    m_accum_y_motion_internal   ( 0.0 ),
    m_dirty                     ( true ),
    m_ignore_next_motion        ( false )
{
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
MouseActuator::MouseActuator(const MouseActuator& other, const osg::CopyOp& copyop):
    Actuator                    ( other, copyop ),
    m_mode                      ( other.m_mode ),
    m_use_axis_x                ( other.m_use_axis_x ),
    m_sensitivity_x             ( other.m_sensitivity_x ),
    m_threshold_x               ( other.m_threshold_x ),
    m_min_x                     ( other.m_min_x ),
    m_max_x                     ( other.m_max_x ),
    m_object_axis_x             ( other.m_object_axis_x ),
    m_local_x                   ( other.m_local_x ),
    m_reset_x                   ( other.m_reset_x ),
    m_use_axis_y                ( other.m_use_axis_y ),
    m_sensitivity_y             ( other.m_sensitivity_y ),
    m_threshold_y               ( other.m_threshold_y ),
    m_min_y                     ( other.m_min_y ),
    m_max_y                     ( other.m_max_y ),
    m_object_axis_y             ( other.m_object_axis_y ),
    m_local_y                   ( other.m_local_y ),
    m_reset_y                   ( other.m_reset_y ),
    m_accum_x_internal          ( other.m_accum_x_internal ),
    m_accum_y_internal          ( other.m_accum_y_internal ),
    m_accum_x_motion_internal   ( other.m_accum_x_motion_internal ),
    m_accum_y_motion_internal   ( other.m_accum_y_motion_internal ),
    m_prev_position             ( other.m_prev_position ),
    m_dirty                     ( other.m_dirty ),
    m_ignore_next_motion        ( other.m_ignore_next_motion )
{
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
MouseActuator::~MouseActuator(void)
{
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
void
MouseActuator::operator()( osgODE::ODEObject* object, Game* game )
{
    (void) game ;


    osgODE::RigidBody*  body = object->asRigidBody() ;

    PS_ASSERT1( body ) ;


    if( isActive() ) {


        if( ! m_ignore_next_motion ) {

            switch( m_mode )
            {
                case    LOOK:   look( body ) ;          break ;
                case    MOVE:   move( body ) ;          break ;

                default:        PS_ASSERT1( false ) ;   break ;
            }
        } else {
            m_ignore_next_motion = false ;
        }



    } else {

        m_dirty = true ;

        m_accum_x_motion_internal = 0.0 ;
        m_accum_y_motion_internal = 0.0 ;

    }
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
void
MouseActuator::look( osgODE::RigidBody* body )
{
    PS_ASSERT1( body != NULL ) ;

    osgODE::World*  world = body->getWorld() ;
    PS_ASSERT1( world != NULL ) ;

    osgODE::Events* events = world->getFrontEventsBuffer() ;
    PS_ASSERT1( events != NULL ) ;


    osg::Vec2   cur_position ;

    const osgODE::Events::EventList&    event_list = events->getEventList() ;

    for(unsigned int i=0; i<event_list.size(); i++) {

        switch( event_list[i].first->getEventType() )
        {
            case osgGA::GUIEventAdapter::MOVE:
            case osgGA::GUIEventAdapter::DRAG:
            {
                cur_position = osg::Vec2( event_list[i].first->getXnormalized(), event_list[i].first->getYnormalized() ) ;


                if( m_reset_x | m_reset_y ) {

                    osg::Vec2   reset_position = cur_position ;

                    if( m_reset_x ) {
                        reset_position.x() = 0.5 * ( event_list[i].first->getXmin() + event_list[i].first->getXmax() ) ;
                    }

                    if( m_reset_y ) {
                        reset_position.y() = 0.5 * ( event_list[i].first->getYmin() + event_list[i].first->getYmax() ) ;
                    }

                    dynamic_cast<osgGA::GUIActionAdapter*>(event_list[i].second.get())->requestWarpPointer( reset_position.x(), reset_position.y() ) ;

                    m_prev_position.set( 0.0, 0.0 ) ;

                    m_ignore_next_motion = true ;

                }
            }
            break ;

            default:
            break ;
        }

    }


    if( m_dirty ) {
        m_prev_position = cur_position ;
        m_dirty = false ;
    }

    osg::Vec2   dist = cur_position - m_prev_position ;

    m_prev_position = cur_position ;



    osg::Quat   rotation = body->getQuaternion() ;



    if( m_use_axis_x ) {

        osg::Vec3   axis = m_local_x ? body->getQuaternion() * m_object_axis_x : m_object_axis_x ;

        rotation = rotation * osg::Quat( computeX( dist.x() ), axis ) ;

    }



    if( m_use_axis_y ) {

        osg::Vec3   axis = m_local_y ? body->getQuaternion() * m_object_axis_y : m_object_axis_y ;

        rotation = rotation * osg::Quat( computeY( dist.y() ), axis ) ;

    }


    body->setQuaternion( rotation ) ;
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
void
MouseActuator::move( osgODE::RigidBody* body )
{
    PS_ASSERT1( body != NULL ) ;

    osgODE::World*  world = body->getWorld() ;
    PS_ASSERT1( world != NULL ) ;

    osgODE::Events* events = world->getFrontEventsBuffer() ;
    PS_ASSERT1( events != NULL ) ;


    osg::Vec2   cur_position ;

    const osgODE::Events::EventList&    event_list = events->getEventList() ;

    for(unsigned int i=0; i<event_list.size(); i++) {

        switch( event_list[i].first->getEventType() )
        {
            case osgGA::GUIEventAdapter::MOVE:
            case osgGA::GUIEventAdapter::DRAG:
            {
                cur_position = osg::Vec2( event_list[i].first->getXnormalized(), event_list[i].first->getYnormalized() ) ;


                if( m_reset_x | m_reset_y ) {

                    osg::Vec2   reset_position = cur_position ;

                    if( m_reset_x ) {
                        reset_position.x() = 0.5 * ( event_list[i].first->getXmin() + event_list[i].first->getXmax() ) ;
                    }

                    if( m_reset_y ) {
                        reset_position.y() = 0.5 * ( event_list[i].first->getYmin() + event_list[i].first->getYmax() ) ;
                    }

                    dynamic_cast<osgGA::GUIActionAdapter*>(event_list[i].second.get())->requestWarpPointer( reset_position.x(), reset_position.y() ) ;

                    m_prev_position.set( 0.0, 0.0 ) ;

                    m_ignore_next_motion = true ;

                }
            }
            break ;

            default:
            break ;
        }

    }


    if( m_dirty ) {
        m_prev_position = cur_position ;
        m_dirty = false ;
    }


    osg::Vec2   dist = cur_position - m_prev_position ;

    m_prev_position = cur_position ;



    osg::Vec3   position = body->getPosition() ;



    if( m_use_axis_x ) {

        osg::Vec3   axis = m_local_x ? body->getQuaternion() * m_object_axis_x : m_object_axis_x ;

        position += axis * computeX( dist.x() ) ;

    }



    if( m_use_axis_y ) {

        osg::Vec3   axis = m_local_y ? body->getQuaternion() * m_object_axis_y : m_object_axis_y ;

        position += axis * computeY( dist.y() ) ;

    }


    body->setPosition( position ) ;
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
ooReal
MouseActuator::computeX( ooReal dist )
{
    m_accum_x_motion_internal += osg::absolute( dist ) ;


    if( m_accum_x_motion_internal <= m_threshold_x ) {
        dist = 0.0 ;
    }


    ooReal      D = m_sensitivity_x * dist ;

    m_accum_x_internal += D ;


    if( m_accum_x_internal > m_max_x ) {
        D += m_max_x - m_accum_x_internal ;
        m_accum_x_internal = m_max_x ;

    } else if ( m_accum_x_internal < m_min_x ) {
        D += m_min_x - m_accum_x_internal ;
        m_accum_x_internal = m_min_x ;

    }


    return D ;
}
/* ....................................................................... */
/* ======================================================================= */




/* ======================================================================= */
/* ....................................................................... */
ooReal
MouseActuator::computeY( ooReal dist )
{
    m_accum_y_motion_internal += osg::absolute( dist ) ;


    if( m_accum_y_motion_internal <= m_threshold_y ) {
        dist = 0.0 ;
    }


    ooReal      D = m_sensitivity_y * dist ;

    m_accum_y_internal += D ;


    if( m_accum_y_internal > m_max_y ) {
        D += m_max_y - m_accum_y_internal ;
        m_accum_y_internal = m_max_y ;

    } else if ( m_accum_y_internal < m_min_y ) {
        D += m_min_y - m_accum_y_internal ;
        m_accum_y_internal = m_min_y ;

    }


    return D ;
}
/* ....................................................................... */
/* ======================================================================= */
